﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Windows.Forms;

namespace System.ComponentModel.Design.Serialization;

/// <summary>
///  This is a class that derives from DesignerLoader but provides some default functionality.
///  This class tracks changes from the loader host and sets its "Modified" bit to true when a
///  change occurs.  Also, this class implements IDesignerLoaderService to support multiple
///  load dependencies.  To use BaseDesignerLoader, you need to implement the PerformLoad
///  and PerformFlush methods.
/// </summary>
public abstract partial class BasicDesignerLoader : DesignerLoader, IDesignerLoaderService
{
    private static readonly int s_stateLoaded = BitVector32.CreateMask();                                       // Have we loaded, or tried to load, the document?
    private static readonly int s_stateLoadFailed = BitVector32.CreateMask(s_stateLoaded);                      // True if we loaded, but had a fatal error.
    private static readonly int s_stateFlushInProgress = BitVector32.CreateMask(s_stateLoadFailed);             // True if we are in the process of flushing code.
    private static readonly int s_stateModified = BitVector32.CreateMask(s_stateFlushInProgress);               // True if the designer is modified.
    private static readonly int s_stateReloadSupported = BitVector32.CreateMask(s_stateModified);               // True if the serializer supports reload.
    private static readonly int s_stateActiveDocument = BitVector32.CreateMask(s_stateReloadSupported);         // Is this the currently active document?
    private static readonly int s_stateDeferredReload = BitVector32.CreateMask(s_stateActiveDocument);          // Set to true if a reload was requested but we aren't the active doc.
    private static readonly int s_stateReloadAtIdle = BitVector32.CreateMask(s_stateDeferredReload);            // Set if we are waiting to reload at idle.  Prevents multiple idle event handlers.
    private static readonly int s_stateForceReload = BitVector32.CreateMask(s_stateReloadAtIdle);               // True if we should always reload, False if we should check the code dom for changes first.
    private static readonly int s_stateFlushReload = BitVector32.CreateMask(s_stateForceReload);                // True if we should flush before reloading.
    private static readonly int s_stateModifyIfErrors = BitVector32.CreateMask(s_stateFlushReload);             // True if we we should modify the buffer if we have fatal errors after load.
    private static readonly int s_stateEnableComponentEvents = BitVector32.CreateMask(s_stateModifyIfErrors);   // True if we are currently listening to OnComponent* events

    // State for the designer loader.
    private BitVector32 _state;
    private IDesignerLoaderHost? _host;
    private int _loadDependencyCount;
    private string? _baseComponentClassName;
    private bool _hostInitialized;
    private bool _loading;

    // State for serialization.
    private DesignerSerializationManager? _serializationManager;
    private IDisposable? _serializationSession;

    /// <summary>
    ///  Creates a new BasicDesignerLoader
    /// </summary>
    protected BasicDesignerLoader()
    {
        _state[s_stateFlushInProgress] = false;
        _state[s_stateReloadSupported] = true;
        _state[s_stateEnableComponentEvents] = false;
        _hostInitialized = false;
        _loading = false;
    }

    /// <summary>
    ///  This protected property indicates if there have been any
    ///  changes made to the design surface.  The Flush method
    ///  gets the value of this property to determine if it needs
    ///  to generate a code dom tree.  This property is set by
    ///  the designer loader when it detects a change to the
    ///  design surface.  You can override this to perform
    ///  additional work, such as checking out a file from source
    ///  code control.
    /// </summary>
    protected virtual bool Modified
    {
        get => _state[s_stateModified];
        set => _state[s_stateModified] = value;
    }

    /// <summary>
    ///  Returns the loader host that was given to this designer loader. This can be null if BeginLoad has not
    ///  been called yet, or if this designer loader has been disposed.
    /// </summary>
    protected IDesignerLoaderHost LoaderHost
    {
        get
        {
            if (_host is not null)
            {
                return _host;
            }

            ObjectDisposedException.ThrowIf(_hostInitialized, this);
            throw new InvalidOperationException(SR.BasicDesignerLoaderNotInitialized);
        }
    }

    /// <summary>
    ///  Returns true when the designer is in the process of loading.
    ///  Clients that are sinking notifications from the designer often
    ///  want to ignore them while the designer is loading
    ///  and only respond to them if they result from user interactions.
    /// </summary>
    public override bool Loading => _loadDependencyCount > 0 || _loading;

    /// <summary>
    ///  Provides an object whose public properties will be made available to the designer serialization manager's
    ///  Properties property.  The default value of this property is null.
    /// </summary>
    protected object? PropertyProvider
    {
        get
        {
            if (_serializationManager is null)
            {
                throw new InvalidOperationException(SR.BasicDesignerLoaderNotInitialized);
            }

            return _serializationManager.PropertyProvider;
        }
        set
        {
            if (_serializationManager is null)
            {
                throw new InvalidOperationException(SR.BasicDesignerLoaderNotInitialized);
            }

            _serializationManager.PropertyProvider = value;
        }
    }

    /// <summary>
    ///  Calling Reload doesn't actually perform a reload immediately - it just schedules an asynchronous
    ///  reload. This property is used to determine if there is currently a reload pending.
    /// </summary>
    protected bool ReloadPending => _state[s_stateReloadAtIdle];

    /// <summary>
    ///  Called by the designer host to begin the loading process.
    ///  The designer host passes in an instance of a designer loader
    ///  host.  This loader host allows the designer loader to reload
    ///  the design document and also allows the designer loader to indicate
    ///  that it has finished loading the design document.
    /// </summary>
    public override void BeginLoad(IDesignerLoaderHost host)
    {
        ArgumentNullException.ThrowIfNull(host);

        if (_state[s_stateLoaded])
        {
            Exception ex = new InvalidOperationException(SR.BasicDesignerLoaderAlreadyLoaded)
            {
                HelpLink = SR.BasicDesignerLoaderAlreadyLoaded
            };

            throw ex;
        }

        if (_host is not null && _host != host)
        {
            Exception ex = new InvalidOperationException(SR.BasicDesignerLoaderDifferentHost)
            {
                HelpLink = SR.BasicDesignerLoaderDifferentHost
            };

            throw ex;
        }

        _state[s_stateLoaded | s_stateLoadFailed] = false;
        _loadDependencyCount = 0;

        if (_host is null)
        {
            _host = host;
            _hostInitialized = true;
            _serializationManager = new DesignerSerializationManager(_host);

            // Add our services.  We do IDesignerSerializationManager separate because
            // it is not something the user can replace.
            if (TryGetService(out DesignSurfaceServiceContainer? dsc))
            {
                dsc.AddFixedService(typeof(IDesignerSerializationManager), _serializationManager);
            }
            else
            {
                IServiceContainer sc = GetRequiredService<IServiceContainer>();
                sc.AddService<IDesignerSerializationManager>(_serializationManager);
            }

            Initialize();
            host.Activated += new EventHandler(OnDesignerActivate);
            host.Deactivated += new EventHandler(OnDesignerDeactivate);
        }

        // Now that we're initialized, let's begin the load.  We assume
        // we support reload until the codeLoader tells us we
        // can't.  That way, we will do the reload if we didn't get a
        // valid loader to start with.
        //
        // StartTimingMark();
        bool successful = true;
        List<object>? localErrorList = null;
        IDesignerLoaderService? ls = GetService<IDesignerLoaderService>();

        try
        {
            if (ls is not null)
            {
                ls.AddLoadDependency();
            }
            else
            {
                _loading = true;
                OnBeginLoad();
            }

            PerformLoad(_serializationManager!);
        }
        catch (Exception e)
        {
            while (e is TargetInvocationException)
            {
                e = e.InnerException!;
            }

            localErrorList = [e];
            successful = false;
        }

        if (ls is not null)
        {
            ls.DependentLoadComplete(successful, localErrorList);
        }
        else
        {
            OnEndLoad(successful, localErrorList);
            _loading = false;
        }
    }

    /// <summary>
    ///  Disposes this designer loader.  The designer host will call
    ///  this method when the design document itself is being destroyed.
    ///  Once called, the designer loader will never be called again.
    ///  This implementation removes any previously added services.  It
    ///  does not flush changes, which allows for fast teardown of a
    ///  designer that wasn't saved.
    /// </summary>
    public override void Dispose()
    {
        if (_state[s_stateReloadAtIdle])
        {
            Application.Idle -= new EventHandler(OnIdle);
        }

        UnloadDocument();

        if (TryGetService(out IComponentChangeService? cs))
        {
            cs.ComponentAdded -= new ComponentEventHandler(OnComponentAdded);
            cs.ComponentAdding -= new ComponentEventHandler(OnComponentAdding);
            cs.ComponentRemoving -= new ComponentEventHandler(OnComponentRemoving);
            cs.ComponentRemoved -= new ComponentEventHandler(OnComponentRemoved);
            cs.ComponentChanged -= new ComponentChangedEventHandler(OnComponentChanged);
            cs.ComponentChanging -= new ComponentChangingEventHandler(OnComponentChanging);
            cs.ComponentRename -= new ComponentRenameEventHandler(OnComponentRename);
        }

        if (_host is not null)
        {
            _host.RemoveService<IDesignerLoaderService>();
            _host.Activated -= new EventHandler(OnDesignerActivate);
            _host.Deactivated -= new EventHandler(OnDesignerDeactivate);
            _host = null;
        }
    }

    /// <summary>
    ///  The designer host will call this periodically when it wants to
    ///  ensure that any changes that have been made to the document
    ///  have been saved by the designer loader.  This method allows
    ///  designer loaders to implement a lazy-write scheme to improve
    ///  performance.  This designer loader implements lazy writes by
    ///  listening to component change events.  If a component has
    ///  changed it sets a "modified" bit.  When Flush is called the
    ///  loader will write out a new code dom tree.
    /// </summary>
    public override void Flush()
    {
        if (_state[s_stateFlushInProgress] || !_state[s_stateLoaded] || !Modified)
        {
            return;
        }

        _state[s_stateFlushInProgress] = true;
        Cursor? oldCursor = Cursor.Current;
        Cursor.Current = Cursors.WaitCursor;

        try
        {
            IDesignerLoaderHost? host = _host;
            Debug.Assert(host is not null, "designer loader was asked to flush after it has been disposed.");

            // If the host has a null root component, it probably failed
            // its last load.  In that case, there is nothing to flush.
            bool shouldChangeModified = true;

            if (host?.RootComponent is not null)
            {
                using (_serializationManager!.CreateSession())
                {
                    try
                    {
                        PerformFlush(_serializationManager);
                    }
                    catch (CheckoutException)
                    {
                        shouldChangeModified = false; // don't need to report that one it already has shown an error message
                        throw;
                    }
                    catch (Exception ex)
                    {
                        _serializationManager.Errors.Add(ex);
                    }

                    ICollection errors = _serializationManager.Errors;

                    if (errors is not null && errors.Count > 0)
                    {
                        ReportFlushErrors(errors);
                    }
                }
            }

            if (shouldChangeModified)
            {
                Modified = false;
            }
        }
        finally
        {
            _state[s_stateFlushInProgress] = false;
            Cursor.Current = oldCursor;
        }
    }

    /// <summary>
    ///  Helper method that gives access to the service provider.
    /// </summary>
    protected object? GetService(Type serviceType)
    {
        object? service = null;

        if (_host is not null)
        {
            service = _host.GetService(serviceType);
        }

        return service;
    }

    private protected T? GetService<T>() where T : class
        => GetService(typeof(T)) as T;

    private protected bool TryGetService<T>([NotNullWhen(true)] out T? service) where T : class
    {
        service = GetService<T>();
        return service is not null;
    }

    /// <summary>
    ///  Simple helper routine that will throw an exception if we need a service, but cannot get
    ///  to it.  You should only throw for missing services that are absolutely essential for
    ///  operation.  If there is a way to gracefully degrade, then you should do it.
    /// </summary>
    private protected T GetRequiredService<T>() where T : class
    {
        return GetService<T>() ??
               throw new InvalidOperationException(string.Format(SR.BasicDesignerLoaderMissingService, typeof(T).Name))
               {
                   HelpLink = SR.BasicDesignerLoaderMissingService
               };
    }

    /// <summary>
    ///  This method is called immediately after the first time
    ///  BeginLoad is invoked.  This is an appropriate place to
    ///  add custom services to the loader host.  Remember to
    ///  remove any custom services you add here by overriding
    ///  Dispose.
    /// </summary>
    protected virtual void Initialize() => LoaderHost.AddService<IDesignerLoaderService>(this);

    /// <summary>
    ///  This method an be overridden to provide some intelligent
    ///  logic to determine if a reload is required.  This method is
    ///  called when someone requests a reload but doesn't force
    ///  the reload.  It gives the loader an opportunity to scan
    ///  the underlying storage to determine if a reload is actually
    ///  needed.  The default implementation of this method always
    ///  returns true.
    /// </summary>
    protected virtual bool IsReloadNeeded() => true;

    /// <summary>
    ///  This method should be called by the designer loader service
    ///  when the first dependent load has started.  This initializes
    ///  the state of the code dom loader and prepares it for loading.
    ///  By default, the designer loader provides
    ///  IDesignerLoaderService itself, so this is called automatically.
    ///  If you provide your own loader service, or if you choose not
    ///  to provide a loader service, you are responsible for calling
    ///  this method.  BeginLoad will automatically call this, either
    ///  indirectly by calling AddLoadDependency if IDesignerLoaderService
    ///  is available, or directly if it is not.
    /// </summary>
    protected virtual void OnBeginLoad()
    {
        _serializationSession = _serializationManager!.CreateSession();
        _state[s_stateLoaded] = false;

        // Make sure that we're removed any event sinks we added after we finished the load.
        // Make sure that we're removed any event sinks we added after we finished the load.
        EnableComponentNotification(false);

        if (!TryGetService(out IComponentChangeService? componentChangeService))
        {
            return;
        }

        componentChangeService.ComponentAdded -= new ComponentEventHandler(OnComponentAdded);
        componentChangeService.ComponentAdding -= new ComponentEventHandler(OnComponentAdding);
        componentChangeService.ComponentRemoving -= new ComponentEventHandler(OnComponentRemoving);
        componentChangeService.ComponentRemoved -= new ComponentEventHandler(OnComponentRemoved);
        componentChangeService.ComponentChanged -= new ComponentChangedEventHandler(OnComponentChanged);
        componentChangeService.ComponentChanging -= new ComponentChangingEventHandler(OnComponentChanging);
        componentChangeService.ComponentRename -= new ComponentRenameEventHandler(OnComponentRename);
    }

    /// <summary>
    /// This method can be used to Enable or Disable component notification by the DesignerLoader.
    /// </summary>
    protected virtual bool EnableComponentNotification(bool enable)
    {
        bool previouslyEnabled = _state[s_stateEnableComponentEvents];

        if (!previouslyEnabled && enable)
        {
            _state[s_stateEnableComponentEvents] = true;
        }
        else if (previouslyEnabled && !enable)
        {
            _state[s_stateEnableComponentEvents] = false;
        }

        return previouslyEnabled;
    }

    /// <summary>
    ///  This method is called immediately before the document is unloaded.
    ///  The document may be unloaded in preparation for reload, or
    ///  if the document failed the load.  If you added document-specific
    ///  services in OnBeginLoad or OnEndLoad, you should remove them
    ///  here.
    /// </summary>
    protected virtual void OnBeginUnload()
    { }

    /// <summary>
    ///  This is called whenever a new component is added to the design surface.
    /// </summary>
    private void OnComponentAdded(object? sender, ComponentEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            Modified = true;
        }
    }

    /// <summary>
    ///  This is called right before a component is added to the design surface.
    /// </summary>
    private void OnComponentAdding(object? sender, ComponentEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            OnModifying();
        }
    }

    /// <summary>
    ///  This is called whenever a component on the design surface changes.
    /// </summary>
    private void OnComponentChanged(object? sender, ComponentChangedEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            Modified = true;
        }
    }

    /// <summary>
    ///  This is called right before a component on the design surface changes.
    /// </summary>
    private void OnComponentChanging(object? sender, ComponentChangingEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            OnModifying();
        }
    }

    /// <summary>
    ///  This is called whenever a component is removed from the design surface.
    /// </summary>
    private void OnComponentRemoved(object? sender, ComponentEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            Modified = true;
        }
    }

    /// <summary>
    ///  This is called right before a component is removed from the design surface.
    /// </summary>
    private void OnComponentRemoving(object? sender, ComponentEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            OnModifying();
        }
    }

    /// <summary>
    ///  Raised by the host when a component is renamed.  Here we modify ourselves
    ///  and then whack the component declaration.  At the next code gen
    ///  cycle we will recreate the declaration.
    /// </summary>
    private void OnComponentRename(object? sender, ComponentRenameEventArgs e)
    {
        // We check the loader host here.  We do not actually listen to
        // this event until the loader has finished loading but if we
        // succeeded the load and the loader then failed later, we might
        // be listening when asked to unload.
        if (_state[s_stateEnableComponentEvents] && !LoaderHost.Loading)
        {
            OnModifying();
            Modified = true;
        }
    }

    /// <summary>
    ///  Called when this document becomes active.  here we check to see if
    ///  someone else has modified the contents of our buffer.  If so, we
    ///  ask the designer to reload.
    /// </summary>
    private void OnDesignerActivate(object? sender, EventArgs e)
    {
        _state[s_stateActiveDocument] = true;

        if (!_state[s_stateDeferredReload] || _host is null)
        {
            return;
        }

        _state[s_stateDeferredReload] = false;
        ReloadOptions flags = ReloadOptions.Default;

        if (_state[s_stateForceReload])
        {
            flags |= ReloadOptions.Force;
        }

        if (!_state[s_stateFlushReload])
        {
            flags |= ReloadOptions.NoFlush;
        }

        if (_state[s_stateModifyIfErrors])
        {
            flags |= ReloadOptions.ModifyOnError;
        }

        Reload(flags);
    }

    /// <summary>
    ///  Called when this document loses activation.  We just remember this
    ///  for later.
    /// </summary>
    private void OnDesignerDeactivate(object? sender, EventArgs e) => _state[s_stateActiveDocument] = false;

    /// <summary>
    ///  This method should be called by the designer loader service
    ///  when all dependent loads have been completed.  This
    ///  "shuts down" the loading process that was initiated by
    ///  BeginLoad.  By default, the designer loader provides
    ///  IDesignerLoaderService itself, so this is called automatically.
    ///  If you provide your own loader service, or if you choose not
    ///  to provide a loader service, you are responsible for calling
    ///  this method.  BeginLoad will automatically call this, either
    ///  indirectly by calling DependentLoadComplete if IDesignerLoaderService
    ///  is available, or directly if it is not.
    /// </summary>
    protected virtual void OnEndLoad(bool successful, ICollection? errors)
    {
        // we don't want successful to be true here if there were load errors.
        // this may allow a situation where we have a dirtied WSOD and might allow
        // a user to save a partially loaded designer docdata.
        successful = successful && (errors is null || errors.Count == 0)
                                && (_serializationManager!.Errors is null
                                || _serializationManager.Errors.Count == 0);
        try
        {
            _state[s_stateLoaded] = true;

            if (!successful && !(TryGetService(out IDesignerLoaderHost2? lh2) && lh2.IgnoreErrorsDuringReload))
            {
                // Can we even show the Continue Ignore errors in DTEL?
                if (lh2 is not null)
                {
                    lh2.CanReloadWithErrors = LoaderHost.RootComponent is not null;
                }

                UnloadDocument();
            }
            else
            {
                successful = true;
            }

            // Inform the serialization manager that we are all done.  The serialization
            // manager clears state at this point to help enforce a stateless serialization
            // mechanism.
            if (errors is not null)
            {
                foreach (object err in errors)
                {
                    _serializationManager!.Errors!.Add(err);
                }
            }

            errors = _serializationManager!.Errors;
        }
        finally
        {
            _serializationSession!.Dispose();
            _serializationSession = null;
        }

        if (successful)
        {
            // After a successful load we will want to monitor a bunch of events so we know when
            // to make the loader modified.

            if (TryGetService(out IComponentChangeService? componentChangeService))
            {
                componentChangeService.ComponentAdded += new ComponentEventHandler(OnComponentAdded);
                componentChangeService.ComponentAdding += new ComponentEventHandler(OnComponentAdding);
                componentChangeService.ComponentRemoving += new ComponentEventHandler(OnComponentRemoving);
                componentChangeService.ComponentRemoved += new ComponentEventHandler(OnComponentRemoved);
                componentChangeService.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged);
                componentChangeService.ComponentChanging += new ComponentChangingEventHandler(OnComponentChanging);
                componentChangeService.ComponentRename += new ComponentRenameEventHandler(OnComponentRename);
            }

            EnableComponentNotification(true);
        }

        LoaderHost.EndLoad(_baseComponentClassName!, successful, errors);

        // if we got errors in the load, set ourselves as modified so we'll regen code.  If this fails, we don't
        // care; the Modified bit was only a hint.
        if (_state[s_stateModifyIfErrors] && errors is not null && errors.Count > 0)
        {
            try
            {
                OnModifying();
                Modified = true;
            }
            catch (CheckoutException ex)
            {
                if (ex != CheckoutException.Canceled)
                {
                    throw;
                }
            }
        }
    }

    /// <summary>
    ///  This method is called in response to a component changing, adding or removing event to indicate
    ///  that the designer is about to be modified.  Those interested in implementing source code
    ///  control may do so by overriding this method.  A call to OnModifying does not mean that the
    ///  Modified property will later be set to true; it is merely an intention to do so.
    /// </summary>
    protected virtual void OnModifying()
    { }

    /// <summary>
    ///  Invoked by the loader host when it actually performs the reload, but before
    ///  the reload actually happens.  Here we unload our part of the loader
    ///  and get us ready for the pending reload.
    /// </summary>
    private void OnIdle(object? sender, EventArgs e)
    {
        Application.Idle -= new EventHandler(OnIdle);

        if (!_state[s_stateReloadAtIdle])
        {
            return;
        }

        _state[s_stateReloadAtIdle] = false;

        // check to see if we are actually the active document.
        DesignSurfaceManager? mgr = GetService<DesignSurfaceManager>();
        DesignSurface? thisSurface = GetService<DesignSurface>();
        Debug.Assert(mgr is not null && thisSurface is not null);

        if (mgr is not null && thisSurface is not null)
        {
            if (!ReferenceEquals(mgr.ActiveDesignSurface, thisSurface))
            {
                // somehow, we got deactivated and weren't told.
                _state[s_stateActiveDocument] = false;
                _state[s_stateDeferredReload] = true; // reload on activate
                return;
            }
        }

        IDesignerLoaderHost host = LoaderHost;

        if (host is null)
        {
            return;
        }

        if (!_state[s_stateForceReload] && !IsReloadNeeded())
        {
            return;
        }

        try
        {
            if (_state[s_stateFlushReload])
            {
                Flush();
            }

            UnloadDocument();
            host.Reload();
        }
        finally
        {
            _state[s_stateForceReload | s_stateModifyIfErrors | s_stateFlushReload] = false;
        }
    }

    /// <summary>
    ///  This method is called when it is time to flush the
    ///  contents of the loader.  You should save any state
    ///  at this time.
    /// </summary>
    protected abstract void PerformFlush(IDesignerSerializationManager serializationManager);

    /// <summary>
    ///  This method is called when it is time to load the
    ///  design surface.  If you are loading asynchronously
    ///  you should ask for IDesignerLoaderService and call
    ///  AddLoadDependency.  When loading asynchronously you
    ///  should at least create the root component during
    ///  PerformLoad.  The DesignSurface is only able to provide
    ///  a view when there is a root component.
    /// </summary>
    protected abstract void PerformLoad(IDesignerSerializationManager serializationManager);

    /// <summary>
    ///  This method schedules a reload of the designer.
    ///  Designer reloading happens asynchronously in order
    ///  to unwind the stack before the reload begins.  If
    ///  force is true, a reload is always performed.  If
    ///  it is false, a reload is only performed if the
    ///  underlying code dom tree has changed in a way that
    ///  would affect the form.
    ///  If flush is true, the designer is flushed before performing
    ///  a reload.  If false, any designer changes are abandoned.
    ///  If ModifyOnError is true, the designer loader will be put
    ///  in the modified state if any errors happened during the
    ///  load.
    /// </summary>
    protected void Reload(ReloadOptions flags)
    {
        _state[s_stateForceReload] = ((flags & ReloadOptions.Force) != 0);
        _state[s_stateFlushReload] = ((flags & ReloadOptions.NoFlush) == 0);
        _state[s_stateModifyIfErrors] = ((flags & ReloadOptions.ModifyOnError) != 0);

        // Our implementation of Reload only reloads if we are the
        // active designer.  Otherwise, we wait until we become
        // active and reload at that time.  We also never do a
        // reload if we are flushing code.
        if (_state[s_stateFlushInProgress])
        {
            return;
        }

        if (!_state[s_stateActiveDocument])
        {
            _state[s_stateDeferredReload] = true;
            return;
        }

        if (_state[s_stateReloadAtIdle])
        {
            return;
        }

        Application.Idle += new EventHandler(OnIdle);
        _state[s_stateReloadAtIdle] = true;
    }

    /// <summary>
    ///  This method is called during flush if one or more errors occurred while
    ///  flushing changes.  The values in the errors collection may either be
    ///  exceptions or objects whose ToString value describes the error.  The default
    ///  implementation of this method takes last exception in the collection and
    ///  raises it as an exception.
    /// </summary>
    protected virtual void ReportFlushErrors(ICollection errors)
    {
        object? lastError = null;

        foreach (object e in errors)
        {
            lastError = e;
        }

        Debug.Assert(lastError is not null, "Someone embedded a null in the error collection");

        if (lastError is null)
        {
            return;
        }

        Exception? ex = lastError as Exception;

        ex ??= new InvalidOperationException(lastError.ToString());

        throw ex;
    }

    /// <summary>
    ///  This property provides the name the designer surface
    ///  will use for the base class.  Normally this is a fully
    ///  qualified name such as "Project1.Form1".  You should set
    ///  this before finishing the load.  Generally this is set
    ///  during PerformLoad.
    /// </summary>
    protected void SetBaseComponentClassName(string name)
    {
        ArgumentNullException.ThrowIfNull(name);

        _baseComponentClassName = name;
    }

    /// <summary>
    ///  This method will be called when the document is to be unloaded.  It
    ///  does not dispose us, but it gets us ready for a dispose or a reload.
    /// </summary>
    private void UnloadDocument()
    {
        OnBeginUnload();
        _state[s_stateLoaded] = false;
        _baseComponentClassName = null;
    }

    /// <summary>
    ///  Adds a load dependency to this loader.  This indicates that some other
    ///  object is also participating in the load, and that the designer loader
    ///  should not call EndLoad on the loader host until all load dependencies
    ///  have called DependentLoadComplete on the designer loader.
    /// </summary>
    void IDesignerLoaderService.AddLoadDependency()
    {
        if (_serializationManager is null)
        {
            throw new InvalidOperationException();
        }

        if (_loadDependencyCount++ == 0)
        {
            OnBeginLoad();
        }
    }

    /// <summary>
    ///  This is called by any object that has previously called
    ///  AddLoadDependency to signal that the dependent load has completed.
    ///  The caller should pass either an empty collection or null to indicate
    ///  a successful load, or a collection of exceptions that indicate the
    ///  reason(s) for failure.
    /// </summary>
    void IDesignerLoaderService.DependentLoadComplete(bool successful, ICollection? errorCollection)
    {
        if (_loadDependencyCount == 0)
        {
            throw new InvalidOperationException();
        }

        // If the dependent load failed, remember it.  There may be multiple
        // dependent loads.  If any one fails, we're sunk.
        if (!successful)
        {
            _state[s_stateLoadFailed] = true;
        }

        if (--_loadDependencyCount == 0)
        {
            // We have just completed the last dependent load.  Report this.
            OnEndLoad(!_state[s_stateLoadFailed], errorCollection);
            return;
        }

        if (errorCollection is null)
        {
            return;
        }

        // Otherwise, add these errors to the serialization manager.
        foreach (object err in errorCollection)
        {
            _serializationManager!.Errors.Add(err);
        }
    }

    /// <summary>
    ///  This can be called by an outside object to request that the loader
    ///  reload the design document.  If it supports reloading and wants to
    ///  comply with the reload, the designer loader should return true.  Otherwise
    ///  it should return false, indicating that the reload will not occur.
    ///  Callers should not rely on the reload happening immediately; the
    ///  designer loader may schedule this for some other time, or it may
    ///  try to reload at once.
    /// </summary>
    bool IDesignerLoaderService.Reload()
    {
        if (!_state[s_stateReloadSupported] || _loadDependencyCount != 0)
        {
            return false;
        }

        Reload(ReloadOptions.Force);

        return true;
    }
}
